package com.study.easyexcel.business.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.ListUtils;
import com.study.easyexcel.api.listener.HealthMonitoring;
import com.study.easyexcel.api.listener.ValidListener;
import com.study.easyexcel.business.dao.WorkDao;
import com.study.easyexcel.business.entity.Traveler;
import com.study.easyexcel.common.constant.ExcelConstants;
import com.study.easyexcel.common.exception.CheckedException;
import com.study.easyexcel.common.exception.ExcelImportException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 回调监听器不能被spring管理，要每次读取excel都要new；如果需要用到被spring管理的对象，可以通过构造函数传入
 *
 * @author yuhuan
 * @date 2023/5/8
 */
public class SimpleImportListener extends HealthMonitoring<Traveler> implements ValidListener<Traveler> {
    private static final Logger logger = LoggerFactory.getLogger(SimpleImportListener.class);
    /**
     * 缓存数据量的阈值，达到阈值就清理list ，方便内存回收；防止数据量过大导致内存OOM
     */
    private static final int BATCH_COUNT = 2;
    /**
     * 表头的最大行数
     */
    private static final int HEAD_MAX_ROWS = 2;
    /**
     * 缓存的数据
     */
    private List<Traveler> cachedList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 数据持久化
     */
    private final WorkDao workWarehouse;
    /**
     * 事务管理，如果涉及多表处理，可以通过编程式事务保证数据一致性
     */
    private final TransactionTemplate transactionTemplate;
    /**
     * 实时表头位置，记录调用到第几行表头
     */
    private int realTimeHead = 1;

    public SimpleImportListener(WorkDao roomWarehouse, TransactionTemplate transactionTemplate) {
        this.workWarehouse = roomWarehouse;
        this.transactionTemplate = transactionTemplate;
    }

    /**
     * 解析表头数据，每行表头调用一次
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        Map<Integer, String> map = ConverterUtils.convertToStringMap(headMap, context);
        logger.info("解析头数据:{}", map.values());
        if (HEAD_MAX_ROWS > realTimeHead) {
            realTimeHead++;
        } else if (HEAD_MAX_ROWS == realTimeHead) {
            invokeHeadMap(map, context);
        } else {
            throw new ExcelImportException("导入失败。使用的模板与导出的模板不一致，请重新下载模板！");
        }
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        super.invokeHeadMap(headMap, context);
        List<String> validHead = getValidHead(Traveler.class);
        logger.info("验证头:{}", validHead);
        // 这里要移除所有的空元素
        validHead.removeAll(Collections.singletonList(null));
        if (ObjectUtils.isEmpty(validHead)) {
            throw new ExcelImportException("未检查到字段上的列名信息！");
        }
        int olen = validHead.size();
        Collection<String> heads = headMap.values();
        logger.info("导入头:{}", heads);
        heads.removeAll(Collections.singletonList(null));
        if (validHead.size() != headMap.size()) {
            throw new ExcelImportException("导入失败。使用的模板与导出的模板不一致，请重新下载模板！");
        }
        validHead.removeAll(heads);
        if (validHead.size() == olen) {
            throw new ExcelImportException("导入失败，请勿随意修改导入模板的列名或者行头！前" + HEAD_MAX_ROWS + "行检测不到列名：" + validHead);
        }
        if (validHead.size() != 0) {
            throw new ExcelImportException("导入失败，请勿随意修改导入模板的列名！前" + HEAD_MAX_ROWS + "行均属于数据头，篡改的列名：" + validHead);
        }
    }

    /**
     * 每解析一条数据都会调用一次，可选择抛出异常
     */
    @Override
    public void invoke(Traveler excelData, AnalysisContext analysisContext) throws ExcelImportException {
        try {
            // 数据格式验证
            validAnalysis(excelData);
        } catch (CheckedException e) {
            throw new ExcelImportException(excelData.getName() + "导入失败，原因是：" + e.getMessage());
        }
        if (cachedList.size() < BATCH_COUNT) {
            this.cachedList.add(excelData);
            return;
        }
        // 达到阈值则保存一次数据，建议执行批量插入的SQL
        logger.info("触发数据缓存阈值，执行数据持久化");
        String execute = transactionTemplate.execute((status -> {
            try {
                for (Traveler traveler : this.cachedList) {
                    workWarehouse.save(traveler);
                    readFamily(traveler.getFamily());
                }
            } catch (ExcelImportException e) {
                // 事务回滚
                status.setRollbackOnly();
                return e.getMessage();
            }
            return "";
        }));
        assert execute != null;
        if (execute.length() > 0) {
            throw new ExcelImportException(excelData.getName() + "导入失败，原因是：" + execute);
        }
        // 存储完成清理 list
        this.cachedList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        this.cachedList.add(excelData);
    }

    /**
     * 每个sheet读取完毕后调用一次
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 保存余留数据到数据库，缓存的阈值为1时没有任何意义
        logger.info("数据解析完成，写入余量数据<{}>条", this.cachedList.size());
        workWarehouse.saveBatch(this.cachedList);
    }

    private void readFamily(String rooms) throws ExcelImportException {
        try {
            if (ObjectUtils.isEmpty(rooms)) {
                return;
            }
            String[] split = rooms.split(ExcelConstants.EXCEL_HYPHEN);
            List<String> family = Arrays.stream(split).distinct().collect(Collectors.toList());
            logger.info("家庭成员-{}", family);
        } catch (Exception e) {
            throw new ExcelImportException("家庭信息导入异常，" + e.getMessage());
        }
    }
}
